using System;
using System.Collections;
using System.Xml;
using System.Xml.XPath;
using MessageGateway;

namespace Resequencer
{
	public delegate void OnResequencerMessageReceivedEvent(XmlDocument msg, int seqNum);

	public delegate void OnResequencerMessageSentEvent(XmlDocument msg, int seqNum);

	public class ResequencerComponent : SimpleFilter
	{
		protected OnResequencerMessageReceivedEvent receivedEvent;
		protected OnResequencerMessageSentEvent sentEvent;

		private readonly string xpath;
		int lastSeen = 0;
		private SortedList messageBuffer = new SortedList();

		public ResequencerComponent(IMessageReceiver receiver, IMessageSender sender, string xpath) : base (receiver, sender)
		{
			this.xpath = xpath;
			receivedEvent = new OnResequencerMessageReceivedEvent(NullEvent);
			sentEvent = new OnResequencerMessageSentEvent(NullEvent);
		}

        public ResequencerComponent(string receiver, string sender, string xpath, IMessageGatewayFactory factory) :
            this (factory.GetReceiverInstance(receiver), factory.GetSenderInstance(sender), xpath)
        {}

		private void NullEvent(XmlDocument message, int seqNum){}

		protected override void OnMessage(XmlDocument msg)
		{
			int seqNum = GetSequenceNumber(msg);
			receivedEvent(msg, seqNum);
			messageBuffer.Add(seqNum, msg);
			SendSequencedMessages();
		}

		private void SendSequencedMessages()
		{
			XmlDocument next;
			while ((next = (XmlDocument) messageBuffer[nextSeqNum()]) != null)
			{
				messageBuffer.Remove(nextSeqNum());
				output.Send(next);
				sentEvent(next, nextSeqNum());
				lastSeen = nextSeqNum();
			}
		}

		private int GetSequenceNumber(XmlDocument msg)
		{
			XPathNavigator nav = msg.CreateNavigator();
			object result = nav.Evaluate(xpath);
			string seqNumStr = "";
			if (result is string)
			{
				seqNumStr = (string) result;
			}
			else if (result is XPathNodeIterator)
			{
				XPathNodeIterator iter = (XPathNodeIterator) result;
				if (iter.MoveNext())
					seqNumStr = iter.Current.Value;
			}
			else
			{
				throw new ApplicationException("Could not find seqNum with xpath " + xpath);
			}
			return Int32.Parse(seqNumStr);
		}

		private int nextSeqNum()
		{
			return lastSeen + 1;
		}

		public void AddEventHandler(OnResequencerMessageReceivedEvent receivedEvent)
		{
			this.receivedEvent += receivedEvent;
		}

		public void AddEventHandler(OnResequencerMessageSentEvent sentEvent)
		{
			this.sentEvent += sentEvent;
		}
	}
}